package think.in.java.concurrency.PractiseOneMoreTime;

import java.util.Stack;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Modify Restaurant.java to use explicit Lock and Condition objects.
 * Note, the when use the condition, it must be placed in the lock block, and which is alike the wait and notify, await and signal must happen after the lock obtain.
 */
public class Exercise27RestaurantWithLockCondition {
    private Meal meal;
    private Waiter waiter;
    private Chef chef;
    private BusyBoby busyBoby;
    Stack stack;


    public Chef getChef() {
        return chef;
    }

    public void setChef(Chef chef) {
        this.chef = chef;
    }

    public Meal getMeal() {
        return meal;
    }

    public void setMeal(Meal meal) {
        this.meal = meal;
    }

    public Waiter getWaiter() {
        return waiter;
    }

    public void setWaiter(Waiter waiter) {
        this.waiter = waiter;
    }

    public static void main(String[] args) throws InterruptedException {
        BlockingQueue blockingQueue;
        Exercise27RestaurantWithLockCondition restaurantWaitNotify = new Exercise27RestaurantWithLockCondition();
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        Waiter waiter1 = new Waiter(restaurantWaitNotify);
        Chef chef1 = new Chef(restaurantWaitNotify);
        BusyBoby busyBoby1 = new BusyBoby(restaurantWaitNotify);
        restaurantWaitNotify.setChef(chef1);
        restaurantWaitNotify.setWaiter(waiter1);
        restaurantWaitNotify.setBusyBoby(busyBoby1);
        executorService.execute(busyBoby1);
        executorService.execute(waiter1);
        executorService.execute(chef1);

        Thread.sleep(1000);
        executorService.shutdownNow();
    }

    public BusyBoby getBusyBoby() {
        return busyBoby;
    }

    public void setBusyBoby(BusyBoby busyBoby) {
        this.busyBoby = busyBoby;
    }




}

class Meal {

}

class BusyBoby implements Runnable {
    Exercise27RestaurantWithLockCondition restaurantWaitNotify;
    ReentrantLock busyLock = new ReentrantLock();
    private Condition busyCondition = busyLock.newCondition();



    BusyBoby(Exercise27RestaurantWithLockCondition restaurantWaitNotify) {
        this.restaurantWaitNotify = restaurantWaitNotify;
    }
    public void await() throws InterruptedException {
        try{
            busyLock.lock();
            getBusyCondition().await();
        }finally {
            busyLock.unlock();
        }
    }

    public void singal() throws InterruptedException {
        try{
            busyLock.lock();
            getBusyCondition().signal();
        }finally {
            busyLock.unlock();
        }
    }

    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                await();
                clean();

            }
        } catch (InterruptedException e) {
            System.out.println("Restaurant closed.");
        }
    }

    private void clean() throws InterruptedException {
        Thread.yield();

        System.out.println(Thread.currentThread().getName() + " cleaning.");
        Thread.sleep(50);
        System.out.println(Thread.currentThread().getName() + " cleaned.");

    }

    public Condition getBusyCondition() {
        return busyCondition;
    }

    public void setBusyCondition(Condition busyCondition) {
        this.busyCondition = busyCondition;
    }
}

class Chef implements Runnable {
    Exercise27RestaurantWithLockCondition restaurantWaitNotify;
    ReentrantLock chefLock = new ReentrantLock();
    Condition chefCondition = chefLock.newCondition();

    public Condition getChefCondition() {
        return chefCondition;
    }

    public void setChefCondition(Condition chefCondition) {
        this.chefCondition = chefCondition;
    }

    Chef(Exercise27RestaurantWithLockCondition restaurantWaitNotify) {
        this.restaurantWaitNotify = restaurantWaitNotify;
    }

    public void await() throws InterruptedException {
        try{
            chefLock.lock();
            getChefCondition().await();
        }
        finally {
            chefLock.unlock();
        }
    }

    public void singal() throws InterruptedException {
        try{
            chefLock.lock();
            getChefCondition().signal();
        }
        finally {
            chefLock.unlock();
        }
    }

    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                if (restaurantWaitNotify.getMeal() != null) {
                    await();

                }
                cook();
                restaurantWaitNotify.getWaiter().singal();
            }
        } catch (InterruptedException e) {
            System.out.println("Restaurant closed.");
        }
    }

    private void cook() throws InterruptedException {
        Thread.yield();

        System.out.println(Thread.currentThread().getName() + " Cooking.");
        Thread.sleep(100);
        restaurantWaitNotify.setMeal(new Meal());
        System.out.println(Thread.currentThread().getName() + " cooked.");

    }
}

class Waiter implements Runnable {
    Exercise27RestaurantWithLockCondition restaurantWaitNotify;
    ReentrantLock waiterLock = new ReentrantLock();
    Condition waiterCondition = waiterLock.newCondition();

    public Condition getWaiterCondition() {
        return waiterCondition;
    }


    Waiter(Exercise27RestaurantWithLockCondition restaurantWaitNotify) {
        this.restaurantWaitNotify = restaurantWaitNotify;
    }

    public void await() throws InterruptedException {
        try{
            waiterLock.lock();
            getWaiterCondition().await();
        }finally {
            waiterLock.unlock();
        }
    }

    public void singal() throws InterruptedException {
        try{
            waiterLock.lock();
            getWaiterCondition().signal();
        }finally {
            waiterLock.unlock();
        }
    }

    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                if (restaurantWaitNotify.getMeal() == null) {
                    await();
                }
                deliver();
                restaurantWaitNotify.getChef().singal();
                restaurantWaitNotify.getBusyBoby().singal();
            }
        } catch (InterruptedException e) {
            System.out.println("Restaurant closed.");
        }
    }

    private void deliver() throws InterruptedException {
        Thread.yield();
//        try {
        System.out.println(Thread.currentThread().getName() + " delivering.");
        Thread.sleep(100);
        restaurantWaitNotify.setMeal(null);
        System.out.println(Thread.currentThread().getName() + " delivered.");
//        } catch (InterruptedException e) {
// todo note, we should not handlethe interrupt exception in the called method which have sleep, as during sleep, if there is
        //todo a interruption, the signal will be reset when sleep met the exception
//            System.out.println("Restaurant closed.");
//        }
    }
}


